// PART OF THE MACHINE SIMULATION. DO NOT CHANGE.

package nachos.machine;

import nachos.security.*;
import nachos.ag.*;

import java.io.File;

/**
 * The master class of the simulated machine. Processes command line arguments,
 * constructs all simulated hardware devices, and starts the grader.
 */
public final class Machine
{
    /**
     * Nachos main entry point.
     *
     * @param    args    the command line arguments.
     */
    public static void main(final String[] args)
    {
        System.out.print("nachos 5.0j initializing...");

        Lib.assertTrue(Machine.args == null);
        Machine.args = args;

        processArgs();

        Config.load(configFileName);

        // get the current directory (.)
        baseDirectory = new File(new File("").getAbsolutePath());
        // get the nachos directory (./nachos)
        nachosDirectory = new File(baseDirectory, "Nachos\\src\\nachos\\test");

        String testDirectoryName =
                Config.getString("FileSystem.testDirectory");

        // get the test directory
        if (testDirectoryName != null)
        {
            testDirectory = new File(testDirectoryName);
        }
        else
        {
            // use ../test
            testDirectory = new File(baseDirectory.getParentFile(), "test");
        }

        securityManager = new NachosSecurityManager(testDirectory);
        privilege = securityManager.getPrivilege();

        privilege.machine = new MachinePrivilege();

        TCB.givePrivilege(privilege);
        privilege.stats = stats;

        securityManager.enable();
        createDevices();
        checkUserClasses();

        autoGrader = (AutoGrader) Lib.constructObject(autoGraderClassName);

        new TCB().start(new Runnable()
        {
            public void run()
            {
                autoGrader.start(privilege);
            }
        });
    }

    /**
     * Yield to non-Nachos threads. Use in non-preemptive JVM's to give
     * non-Nachos threads a chance to run.
     */
    public static void yield()
    {
        Thread.yield();
    }

    /**
     * Terminate Nachos. Same as <tt>TCB.die()</tt>.
     */
    public static void terminate()
    {
        TCB.die();
    }

    /**
     * Terminate Nachos as the result of an unhandled exception or error.
     *
     * @param    e    the exception or error.
     */
    public static void terminate(Throwable e)
    {
        if (e instanceof ThreadDeath)
            throw (ThreadDeath) e;

        e.printStackTrace();
        terminate();
    }

    /**
     * Print stats, and terminate Nachos.
     */
    public static void halt()
    {
        System.out.print("Machine halting!\n\n");
        stats.print();
        terminate();
    }

    /**
     * Return an array containing all command line arguments.
     *
     * @return the command line arguments passed to Nachos.
     */
    public static String[] getCommandLineArguments()
    {
        String[] result = new String[args.length];

        System.arraycopy(args, 0, result, 0, args.length);

        return result;
    }

    private static void processArgs()
    {
        for (int i = 0; i < args.length; )
        {
            String arg = args[i++];
            if (arg.length() > 0 && arg.charAt(0) == '-')
            {
                if (arg.equals("-d"))
                {
                    Lib.assertTrue(i < args.length, "switch without argument");
                    Lib.enableDebugFlags(args[i++]);
                }
                else if (arg.equals("-h"))
                {
                    System.out.print(help);
                    System.exit(1);
                }
                else if (arg.equals("-m"))
                {
                    Lib.assertTrue(i < args.length, "switch without argument");
                    try
                    {
                        numPhysPages = Integer.parseInt(args[i++]);
                    } catch (NumberFormatException e)
                    {
                        Lib.assertNotReached("bad value for -m switch");
                    }
                }
                else if (arg.equals("-s"))
                {
                    Lib.assertTrue(i < args.length, "switch without argument");
                    try
                    {
                        randomSeed = Long.parseLong(args[i++]);
                    } catch (NumberFormatException e)
                    {
                        Lib.assertNotReached("bad value for -s switch");
                    }
                }
                else if (arg.equals("-x"))
                {
                    Lib.assertTrue(i < args.length, "switch without argument");
                    shellProgramName = args[i++];
                }
                else if (arg.equals("-z"))
                {
                    System.out.print(copyright);
                    System.exit(1);
                }
                // these switches are reserved for the autograder
                else if (arg.equals("-[]"))
                {
                    Lib.assertTrue(i < args.length, "switch without argument");
                    configFileName = args[i++];
                }
                else if (arg.equals("--"))
                {
                    Lib.assertTrue(i < args.length, "switch without argument");
                    autoGraderClassName = args[i++];
                }
            }
        }

        Lib.seedRandom(randomSeed);
    }

    private static void createDevices()
    {
        interrupt = new Interrupt(privilege);
        timer = new Timer(privilege);

        if (Config.getBoolean("Machine.bank"))
            bank = new ElevatorBank(privilege);

        if (Config.getBoolean("Machine.processor"))
        {
            if (numPhysPages == -1)
                numPhysPages = Config.getInteger("Processor.numPhysPages");
            processor = new Processor(privilege, numPhysPages);
        }

        if (Config.getBoolean("Machine.console"))
            console = new StandardConsole(privilege);

        if (Config.getBoolean("Machine.stubFileSystem"))
            stubFileSystem = new StubFileSystem(privilege, testDirectory);

        if (Config.getBoolean("Machine.networkLink"))
            networkLink = new NetworkLink(privilege);
    }

    private static void checkUserClasses()
    {
        System.out.print(" user-check");

        Class aclsInt = (new int[0]).getClass();
        Class clsObject = Lib.loadClass("java.lang.Object");
        Class clsRunnable = Lib.loadClass("java.lang.Runnable");
        Class clsString = Lib.loadClass("java.lang.String");

        Class clsKernel = Lib.loadClass("nachos.machine.Kernel");
        Class clsFileSystem = Lib.loadClass("nachos.machine.FileSystem");
        Class clsRiderControls = Lib.loadClass("nachos.machine.RiderControls");
        Class clsElevatorControls =
                Lib.loadClass("nachos.machine.ElevatorControls");
        Class clsRiderInterface =
                Lib.loadClass("nachos.machine.RiderInterface");
        Class clsElevatorControllerInterface =
                Lib.loadClass("nachos.machine.ElevatorControllerInterface");

        Class clsAlarm = Lib.loadClass("nachos.threads.Alarm");
        Class clsThreadedKernel =
                Lib.loadClass("nachos.threads.ThreadedKernel");
        Class clsKThread = Lib.loadClass("nachos.threads.KThread");
        Class clsCommunicator = Lib.loadClass("nachos.threads.Communicator");
        Class clsSemaphore = Lib.loadClass("nachos.threads.Semaphore");
        Class clsLock = Lib.loadClass("nachos.threads.Lock");
        Class clsCondition = Lib.loadClass("nachos.threads.Condition");
        Class clsCondition2 = Lib.loadClass("nachos.threads.Condition2");
        Class clsRider = Lib.loadClass("nachos.threads.Rider");
        Class clsElevatorController =
                Lib.loadClass("nachos.threads.ElevatorController");

        Lib.checkDerivation(clsThreadedKernel, clsKernel);

        Lib.checkStaticField(clsThreadedKernel, "alarm", clsAlarm);
        Lib.checkStaticField(clsThreadedKernel, "fileSystem", clsFileSystem);

        Lib.checkMethod(clsAlarm, "waitUntil", new Class[]{long.class},
                void.class);

        Lib.checkConstructor(clsKThread, new Class[]{});
        Lib.checkConstructor(clsKThread, new Class[]{clsRunnable});

        Lib.checkStaticMethod(clsKThread, "currentThread", new Class[]{},
                clsKThread);
        Lib.checkStaticMethod(clsKThread, "finish", new Class[]{},
                void.class);
        Lib.checkStaticMethod(clsKThread, "yield", new Class[]{}, void.class);
        Lib.checkStaticMethod(clsKThread, "sleep", new Class[]{}, void.class);

        Lib.checkMethod(clsKThread, "setTarget", new Class[]{clsRunnable},
                clsKThread);
        Lib.checkMethod(clsKThread, "setName", new Class[]{clsString},
                clsKThread);
        Lib.checkMethod(clsKThread, "getName", new Class[]{}, clsString);
        Lib.checkMethod(clsKThread, "fork", new Class[]{}, void.class);
        Lib.checkMethod(clsKThread, "ready", new Class[]{}, void.class);
        Lib.checkMethod(clsKThread, "join", new Class[]{}, void.class);

        Lib.checkField(clsKThread, "schedulingState", clsObject);

        Lib.checkConstructor(clsCommunicator, new Class[]{});
        Lib.checkMethod(clsCommunicator, "speak", new Class[]{int.class},
                void.class);
        Lib.checkMethod(clsCommunicator, "listen", new Class[]{}, int.class);

        Lib.checkConstructor(clsSemaphore, new Class[]{int.class});
        Lib.checkMethod(clsSemaphore, "P", new Class[]{}, void.class);
        Lib.checkMethod(clsSemaphore, "V", new Class[]{}, void.class);

        Lib.checkConstructor(clsLock, new Class[]{});
        Lib.checkMethod(clsLock, "acquire", new Class[]{}, void.class);
        Lib.checkMethod(clsLock, "release", new Class[]{}, void.class);
        Lib.checkMethod(clsLock, "isHeldByCurrentThread", new Class[]{},
                boolean.class);

        Lib.checkConstructor(clsCondition, new Class[]{clsLock});
        Lib.checkConstructor(clsCondition2, new Class[]{clsLock});

        Lib.checkMethod(clsCondition, "sleep", new Class[]{}, void.class);
        Lib.checkMethod(clsCondition, "wake", new Class[]{}, void.class);
        Lib.checkMethod(clsCondition, "wakeAll", new Class[]{}, void.class);
        Lib.checkMethod(clsCondition2, "sleep", new Class[]{}, void.class);
        Lib.checkMethod(clsCondition2, "wake", new Class[]{}, void.class);
        Lib.checkMethod(clsCondition2, "wakeAll", new Class[]{}, void.class);

        Lib.checkDerivation(clsRider, clsRiderInterface);

        Lib.checkConstructor(clsRider, new Class[]{});
        Lib.checkMethod(clsRider, "initialize",
                new Class[]{clsRiderControls, aclsInt}, void.class);

        Lib.checkDerivation(clsElevatorController,
                clsElevatorControllerInterface);

        Lib.checkConstructor(clsElevatorController, new Class[]{});
        Lib.checkMethod(clsElevatorController, "initialize",
                new Class[]{clsElevatorControls}, void.class);
    }

    /**
     * Prevent instantiation.
     */
    private Machine()
    {
    }

    /**
     * Return the hardware interrupt manager.
     *
     * @return the hardware interrupt manager.
     */
    public static Interrupt interrupt()
    {
        return interrupt;
    }

    /**
     * Return the hardware timer.
     *
     * @return the hardware timer.
     */
    public static Timer timer()
    {
        return timer;
    }

    /**
     * Return the hardware elevator bank.
     *
     * @return the hardware elevator bank, or <tt>null</tt> if it is not
     * present.
     */
    public static ElevatorBank bank()
    {
        return bank;
    }

    /**
     * Return the MIPS processor.
     *
     * @return the MIPS processor, or <tt>null</tt> if it is not present.
     */
    public static Processor processor()
    {
        return processor;
    }

    /**
     * Return the hardware console.
     *
     * @return the hardware console, or <tt>null</tt> if it is not present.
     */
    public static SerialConsole console()
    {
        return console;
    }

    /**
     * Return the stub filesystem.
     *
     * @return the stub file system, or <tt>null</tt> if it is not present.
     */
    public static FileSystem stubFileSystem()
    {
        return stubFileSystem;
    }

    /**
     * Return the network link.
     *
     * @return the network link,  or <tt>null</tt> if it is not present.
     */
    public static NetworkLink networkLink()
    {
        return networkLink;
    }

    /**
     * Return the autograder.
     *
     * @return the autograder.
     */
    public static AutoGrader autoGrader()
    {
        return autoGrader;
    }

    private static Interrupt interrupt = null;
    private static Timer timer = null;
    private static ElevatorBank bank = null;
    private static Processor processor = null;
    private static SerialConsole console = null;
    private static FileSystem stubFileSystem = null;
    private static NetworkLink networkLink = null;
    private static AutoGrader autoGrader = null;

    private static String autoGraderClassName = "nachos.ag.AutoGrader";

    /**
     * Return the name of the shell program that a user-programming kernel
     * must run. Make sure <tt>UserKernel.run()</tt> <i>always</i> uses this
     * method to decide which program to run.
     *
     * @return the name of the shell program to run.
     */
    public static String getShellProgramName()
    {
        if (shellProgramName == null)
            shellProgramName = Config.getString("Kernel.shellProgram");

        Lib.assertTrue(shellProgramName != null);
        return shellProgramName;
    }

    private static String shellProgramName = null;

    /**
     * Return the name of the process class that the kernel should use. In
     * the multi-programming project, returns
     * <tt>nachos.userprog.UserProcess</tt>. In the VM project, returns
     * <tt>nachos.vm.VMProcess</tt>. In the networking project, returns
     * <tt>nachos.network.NetProcess</tt>.
     *
     * @return the name of the process class that the kernel should use.
     * @see    nachos.userprog.UserKernel#run
     * @see    nachos.userprog.UserProcess
     * @see    nachos.vm.VMProcess
     * @see    nachos.network.NetProcess
     */
    public static String getProcessClassName()
    {
        if (processClassName == null)
            processClassName = Config.getString("Kernel.processClassName");

        Lib.assertTrue(processClassName != null);
        return processClassName;
    }

    private static String processClassName = null;

    private static NachosSecurityManager securityManager;
    private static Privilege privilege;

    private static String[] args = null;

    private static Stats stats = new Stats();

    private static int numPhysPages = -1;
    private static long randomSeed = 0;

    private static File baseDirectory, nachosDirectory, testDirectory;
    private static String configFileName = "nachos.conf";

    private static final String help =
            "\n" +
                    "Options:\n" +
                    "\n" +
                    "\t-d <debug flags>\n" +
                    "\t\tEnable some debug flags, e.g. -d ti\n" +
                    "\n" +
                    "\t-h\n" +
                    "\t\tPrint this help message.\n" +
                    "\n" +
                    "\t-m <pages>\n" +
                    "\t\tSpecify how many physical pages of memory to simulate.\n" +
                    "\n" +
                    "\t-s <seed>\n" +
                    "\t\tSpecify the seed for the random number generator (seed is a\n" +
                    "\t\tlong).\n" +
                    "\n" +
                    "\t-x <program>\n" +
                    "\t\tSpecify a program that UserKernel.run() should execute,\n" +
                    "\t\tinstead of the value of the configuration variable\n" +
                    "\t\tKernel.shellProgram\n" +
                    "\n" +
                    "\t-z\n" +
                    "\t\tprint the copyright message\n" +
                    "\n" +
                    "\t-- <grader class>\n" +
                    "\t\tSpecify an autograder class to use, instead of\n" +
                    "\t\tnachos.ag.AutoGrader\n" +
                    "\n" +
                    "\t-# <grader arguments>\n" +
                    "\t\tSpecify the argument string to pass to the autograder.\n" +
                    "\n" +
                    "\t-[] <config file>\n" +
                    "\t\tSpecifiy a config file to use, instead of nachos.conf\n" +
                    "";

    private static final String copyright = "\n"
            + "Copyright 1992-2001 The Regents of the University of California.\n"
            + "All rights reserved.\n"
            + "\n"
            + "Permission to use, copy, modify, and distribute this software and\n"
            + "its documentation for any purpose, without fee, and without\n"
            + "written agreement is hereby granted, provided that the above\n"
            + "copyright notice and the following two paragraphs appear in all\n"
            + "copies of this software.\n"
            + "\n"
            + "IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY\n"
            + "PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL\n"
            + "DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS\n"
            + "DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN\n"
            + "ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
            + "\n"
            + "THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY\n"
            + "WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"
            + "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE\n"
            + "SOFTWARE PROVIDED HEREUNDER IS ON AN \"AS IS\" BASIS, AND THE\n"
            + "UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE\n"
            + "MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.\n";

    private static class MachinePrivilege
            implements Privilege.MachinePrivilege
    {
        public void setConsole(SerialConsole console)
        {
            Machine.console = console;
        }
    }

    // dummy variables to make javac smarter
    private static Coff dummy1 = null;
}
